/*
 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.io;

import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;


/**
 * 文件输出流是将数据写入文件或文件描述符的输出流。
 * 文件是否可用或可能被创建取决于底层平台。
 * 特别是一些平台，每次只允许一个FileOutputStream(或其他文件写入对象)打开文件以写入。
 * 在这种情况下，如果所涉及的文件已经打开，则该类中的构造函数将失败。
 *
 * <p>FileOutputStream用于编写原始字节流，比如图像数据。
 * 如果要编写字符流，可以考虑使用FileWriter。
 *
 * @author  Arthur van Hoff
 * @see     java.io.File
 * @see     java.io.FileDescriptor
 * @see     java.io.FileInputStream
 * @see     java.nio.file.Files#newOutputStream
 * @since   JDK1.0
 */
public
class FileOutputStream extends OutputStream
{
    /**
     * 系统依赖的文件描述符
     */
    private final FileDescriptor fd;

    /**
     * 如果文件打开，是为了添加数据，为true
     */
    private final boolean append;

    /**
     * 关联的channel，懒加载
     */
    private FileChannel channel;

    /**
     * 引用文件的路径（如果用文件描述符创建输出流，为null）
     */
    private final String path;

    // 关闭流相关字段，一个为关闭时synchronize的锁，一个是对应的是否
    private final Object closeLock = new Object();
    private volatile boolean closed = false;

    /**
     * 创建一个文件输出流，以写入指定name的文件。
     * 创建一个新的FileDescriptor对象来表示这个文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以name作为参数调用其checkWrite方法。
     * <p>
     * 如果该文件，存在但只是一个目录而不是常规文件，或不存在但不能创建，
     * 或由于任何其他原因无法打开，则会抛出一个FileNotFoundException。
     *
     * @param      name   the system-dependent filename
     * @exception  FileNotFoundException  if the file exists but is a directory
     *                   rather than a regular file, does not exist but cannot
     *                   be created, or cannot be opened for any other reason
     * @exception  SecurityException  if a security manager exists and its
     *               <code>checkWrite</code> method denies write access
     *               to the file.
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     */
    public FileOutputStream(String name) throws FileNotFoundException {
    	// 建立一个new File(name)
    	// append为false，替代型
        this(name != null ? new File(name) : null, false);
    }

    /**
     * 创建一个文件输出流，以使用指定的名称写入文件。
     * 如果第二个参数为真，则字节将被写入文件的末尾而不是开始。
     * 创建一个新的FileDescriptor对象来表示这个文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以name作为参数调用其checkWrite方法。
     * <p>
     * 如果该文件，存在但只是一个目录而不是常规文件，或不存在但不能创建，
     * 或由于任何其他原因无法打开，则会抛出一个FileNotFoundException。
     *
     * @param     name        the system-dependent file name
     * @param     append      if <code>true</code>, then bytes will be written
     *                   to the end of the file rather than the beginning
     * @exception  FileNotFoundException  if the file exists but is a directory
     *                   rather than a regular file, does not exist but cannot
     *                   be created, or cannot be opened for any other reason.
     * @exception  SecurityException  if a security manager exists and its
     *               <code>checkWrite</code> method denies write access
     *               to the file.
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     * @since     JDK1.1
     */
    public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }

    /**
     * 创建文件输出流，以写入由指定file对象表示的文件。
     * 创建一个新的FileDescriptor对象来表示这个文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以name作为参数调用其checkWrite方法。
     * <p>
     * 如果该文件，存在但只是一个目录而不是常规文件，或不存在但不能创建，
     * 或由于任何其他原因无法打开，则会抛出一个FileNotFoundException。
     *
     * @param      file               the file to be opened for writing.
     * @exception  FileNotFoundException  if the file exists but is a directory
     *                   rather than a regular file, does not exist but cannot
     *                   be created, or cannot be opened for any other reason
     * @exception  SecurityException  if a security manager exists and its
     *               <code>checkWrite</code> method denies write access
     *               to the file.
     * @see        java.io.File#getPath()
     * @see        java.lang.SecurityException
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     */
    public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

    /**
     * 创建文件输出流，以写入由指定file对象表示的文件。
     * 如果第二个参数为真，则字节将被写入文件生成器的末尾，而不是开头。
     * 创建一个新的FileDescriptor对象来表示这个文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以name作为参数调用其checkWrite方法。
     * <p>
     * 如果该文件，存在但只是一个目录而不是常规文件，或不存在但不能创建，
     * 或由于任何其他原因无法打开，则会抛出一个FileNotFoundException。
     *
     * @param      file               the file to be opened for writing.
     * @param     append      if <code>true</code>, then bytes will be written
     *                   to the end of the file rather than the beginning
     * @exception  FileNotFoundException  if the file exists but is a directory
     *                   rather than a regular file, does not exist but cannot
     *                   be created, or cannot be opened for any other reason
     * @exception  SecurityException  if a security manager exists and its
     *               <code>checkWrite</code> method denies write access
     *               to the file.
     * @see        java.io.File#getPath()
     * @see        java.lang.SecurityException
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     * @since 1.4
     */
    public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
        String name = (file != null ? file.getPath() : null);
        // 校验
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        // 设置文件描述符fd，append，path
        this.fd = new FileDescriptor();
        fd.attach(this);
        this.append = append;
        this.path = name;
        // 打开指定名称的文件，以便覆盖或追加
        open(name, append);
    }

    /**
     * 创建一个文件输出流，以写入指定的filedescriptor，
     * 该描述符表示与文件系统中实际文件的现有连接。
     * <p>
     * 首先，如果存在安全管理器，则使用文件描述符fdObj参数作为其参数调用其checkWrite方法。
     * <p>
     * 如果fdObj为null，则抛出NullPointerException。
     * <p>
     * 如果fdObj无效，此构造函数不会抛出异常。
     * 但是，如果结果流上的方法尝试对流进行IO操作，则会抛出一个IOException。
     *
     * @param      fdObj   the file descriptor to be opened for writing
     * @exception  SecurityException  if a security manager exists and its
     *               <code>checkWrite</code> method denies
     *               write access to the file descriptor
     * @see        java.lang.SecurityManager#checkWrite(java.io.FileDescriptor)
     */
    public FileOutputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkWrite(fdObj);
        }
        this.fd = fdObj;
        this.append = false;
        this.path = null;

        fd.attach(this);
    }

    /**
     * 打开指定名称的文件，以便覆盖或追加。
     * @param name name of file to be opened
     * @param append whether the file is to be opened in append mode
     */
    private native void open0(String name, boolean append)
        throws FileNotFoundException;

    // 包装本机调用以允许插装
    /**
     * 打开指定名称的文件，以便覆盖或追加。
     * @param name name of file to be opened
     * @param append whether the file is to be opened in append mode
     */
    private void open(String name, boolean append)
        throws FileNotFoundException {
        open0(name, append);
    }

    /**
     * 将指定的字节写入此文件输出流。
     *
     * @param   b   the byte to be written.
     * @param   append   {@code true} if the write operation first
     *     advances the position to the end of file
     */
    private native void write(int b, boolean append) throws IOException;

    /**
     * 将指定的字节写入此文件输出流。
     * 实现OutputSteam的write方法
     *
     * @param      b   the byte to be written.
     * @exception  IOException  if an I/O error occurs.
     */
    public void write(int b) throws IOException {
        write(b, append);
    }

    /**
     * 以字节序列的形式写入一个子数组。
     * 
     * @param b the data to be written
     * @param off the start offset in the data
     * @param len the number of bytes that are written
     * @param append {@code true} to first advance the position to the
     *     end of file
     * @exception IOException If an I/O error has occurred.
     */
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;

    /**
     * 将b.length个字节写入此文件输出流。
     *
     * @param      b   the data.
     * @exception  IOException  if an I/O error occurs.
     */
    public void write(byte b[]) throws IOException {
        writeBytes(b, 0, b.length, append);
    }

    /**
     * 将指定字节数组从off位置开始的len字节写入此文件输出流。
     *
     * @param      b     the data.
     * @param      off   the start offset in the data.
     * @param      len   the number of bytes to write.
     * @exception  IOException  if an I/O error occurs.
     */
    public void write(byte b[], int off, int len) throws IOException {
        writeBytes(b, off, len, append);
    }

    /**
     * 关闭此文件输出流并释放与此流关联的任何系统资源。
     * 此文件输出流不能再用于写入字节。
     *
     * <p> 如果该流有一个相关联的通道，则该通道同样关闭。
     *
     * @exception  IOException  if an I/O error occurs.
     *
     * @revised 1.4
     * @spec JSR-51
     */
    public void close() throws IOException {
    	// 以closeLock为锁，设置closed属性
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        // 关闭channel和fd
        if (channel != null) {
            channel.close();
        }
        // 调用本地方法close0来关闭fd
        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

    /**
     * 回与此流关联的文件描述符。
     *
     * @return  the <code>FileDescriptor</code> object that represents
     *          the connection to the file in the file system being used
     *          by this <code>FileOutputStream</code> object.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileDescriptor
     */
     public final FileDescriptor getFD()  throws IOException {
        if (fd != null) {
            return fd;
        }
        throw new IOException();
     }

    /**
     * 返回与此文件输出流关联的惟一FileChannel对象。
     *
     * <p> 返回通道的初始位置将等于到目前为止写入文件的字节数，
     * 除非该流是append模式，在这种情况下，它将等于文件的大小。
     * 向该流写入字节将相应地增加通道的位置。
     * 通过显式或写入方式改变通道的位置，将改变流的文件位置。
     *
     * @return  the file channel associated with this file output stream
     *
     * @since 1.4
     * @spec JSR-51
     */
    public FileChannel getChannel() {
    	// 保证一个fileOutputStream只对应一个channel
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, false, true, append, this);
            }
            return channel;
        }
    }

    /**
     * 清理到该文件的连接，并确保在没有对该文件流的更多引用时调用该文件输出流的close方法。
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if (fd != null) {
            if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
            	// 如果输出对象是System.out或者err，调用flush方法
            	// 注意：FileOutputStream对象没有覆盖flush方法，所以flush方法还是OutputStream的空方法！
                flush();
            } else {
                /* 如果fd是共享的，那么FileDescriptor中的引用将确保只在安全的情况下调用finalizer。
                 * 所有使用fd的引用都变得不可访问。我们可以调用close()
                 */
                close();
            }
        }
    }

    private native void close0() throws IOException;

    private static native void initIDs();

    static {
        initIDs();
    }

}
